//
// Created by Tony on 2024/5/20.
//

#include "AbsParser.h"
#include "../common/KeyWords.h"
#include "../common/Utils.h"
#include "../lexer/token/NameToken.h"
#include "../lexer/token/PunctToken.h"
#include "ast/leaf/BooleanLiteral.h"
#include "ast/leaf/CharLiteral.h"
#include "ast/leaf/Flt64Literal.h"
#include "ast/leaf/Int64Literal.h"
#include "ast/leaf/Name.h"
#include "ast/leaf/NullLiteral.h"
#include "ast/leaf/Operator.h"
#include "ast/leaf/StringLiteral.h"
#include "ast/list/AccessFlags.h"
#include "ast/list/TypeTag.h"
#include "ast/list/expr/BinaryExpr.h"
#include "ast/list/expr/UnaryExpr.h"
#include <cstring>
#include <memory>
#include <vector>

namespace Parser {

using std::make_shared;

bool AbsParser::isPunct(const shared_ptr<Lexer::Token>& token, const char* punct) {
    if (token == nullptr) {
        return false;
    }

    if (token->type() != Lexer::TokenType::PUNCT) {
        return false;
    }

    auto punctToken = static_pointer_cast<Lexer::PunctToken>(token);
    return strcmp(punctToken->getValue(), punct) == 0;
}

bool AbsParser::skipPunct(const char* punct) {
    auto token = tokenQueue.poll();
    if (isPunct(token, punct)) {
        return true;
    }

    string position;
    if (token == nullptr) {
        position = tokenQueue.getLastPosition().endStr();
    } else {
        position = token->getPosition().startStr();
    }
    setErrMsg("expect punct %s at %s", punct, position.c_str());
    return false;
}

bool AbsParser::isName(const shared_ptr<Lexer::Token>& token, const char* name) {
    if (token == nullptr) {
        return false;
    }

    if (token->type() != Lexer::TokenType::NAME) {
        return false;
    }

    auto nameToken = static_pointer_cast<Lexer::NameToken>(token);
    return nameToken->getValue() == name;
}

bool AbsParser::skipName(const string& name) {
    auto token = tokenQueue.poll();
    if (token != nullptr && token->type() == Lexer::TokenType::NAME) {
        auto nameToken = static_pointer_cast<Lexer::NameToken>(token);
        if (nameToken->getValue() == name) {
            return true;
        }
    }

    string position;
    if (token == nullptr) {
        position = tokenQueue.getLastPosition().endStr();
    } else {
        position = token->getPosition().startStr();
    }
    setErrMsg("expect %s at %s", name.c_str(), position.c_str());
    return false;
}

/**
 * name := NAME
 */
shared_ptr<Name> AbsParser::name() {
    auto token = tokenQueue.poll();
    if (token->type() == Lexer::TokenType::NAME) {
        return make_shared<Name>(token);
    }
    setErrMsg("bad name at %s ", token->getPosition().startStr().c_str());
    return nullptr;
}

/**
 * primary := ( ( expr ) | LITERAL | NAME ) { postfix }
 */
shared_ptr<Ast> AbsParser::primary() {
    shared_ptr<Ast> primaryExpr;
    auto token = tokenQueue.peek();
    if (isPunct(token, "(")) {
        tokenQueue.poll(); // skip "("
        primaryExpr = expr();
        skipPunct(")");
    } else {
        token = tokenQueue.poll();
        switch (token->type()) {
            case Lexer::TokenType::INT64_LITERAL:
                primaryExpr = make_shared<Int64Literal>(token);
                break;
            case Lexer::TokenType::FLT64_LITERAL:
                primaryExpr = make_shared<Flt64Literal>(token);
                break;
            case Lexer::TokenType::CHAR_LITERAL:
                primaryExpr = make_shared<CharLiteral>(token);
                break;
            case Lexer::TokenType::BOOLEAN_LITERAL:
                primaryExpr = make_shared<BooleanLiteral>(token);
                break;
            case Lexer::TokenType::STRING_LITERAL:
                primaryExpr = make_shared<StringLiteral>(token);
                break;
            case Lexer::TokenType::NULL_LITERAL:
                primaryExpr = make_shared<NullLiteral>(token);
                break;
            case Lexer::TokenType::NAME:
                primaryExpr = make_shared<Name>(token);
                break;
            default:
                setErrMsg("syntax error at %s", token->getPosition().startStr().c_str());
                return nullptr;
        }
    }

    while (true) {
        auto postfix = this->postfix();
        if (postfix.empty()) {
            return primaryExpr;
        }

        primaryExpr =
            make_shared<BinaryExpr>(vector<shared_ptr<Ast>>{primaryExpr, postfix[0], postfix[1]});
    }
}

/**
 * factor := ( "-" | "!" | "new" ) primary | primary
 */
shared_ptr<Ast> AbsParser::factor() {
    auto token = tokenQueue.peek();
    if (!(isPunct(token, "-") || isPunct(token, "!") || isPunct(token, "new"))) {
        return primary();
    }

    tokenQueue.poll();
    auto op = make_shared<Operator>(token);
    auto primary = this->primary();
    CHECK_NULL_RETURN(primary, nullptr);

    return make_shared<UnaryExpr>(vector<shared_ptr<Ast>>{op, primary});
}

/**
 * type_tag := ":" NAME
 */
shared_ptr<TypeTag> AbsParser::typeTag() {
    if (!skipPunct(":")) {
        return nullptr;
    }
    auto name = this->name();
    CHECK_NULL_RETURN(name, nullptr);

    return make_shared<TypeTag>(vector<shared_ptr<Ast>>{name});
}

/**
 * access_flags := [ ("public" | "protect" | "private" | "static" | "native") ]
 */
std::shared_ptr<AccessFlags> AbsParser::accessFlags() {
    vector<shared_ptr<Ast>> accessFlags;
    while (true) {
        auto token = tokenQueue.peek();
        if (isName(token, KeyWords::PUBLIC) || isName(token, KeyWords::PROTECT) ||
            isName(token, KeyWords::PRIVATE) || isName(token, KeyWords::STATIC) ||
            isName(token, KeyWords::NATIVE)) {
            accessFlags.push_back(name());
        } else {
            break;
        }
    }
    return make_shared<AccessFlags>(accessFlags);
}

/**
 * postfix := "." name | "(" argument_list | "[" array_index
 */
vector<shared_ptr<Ast>> AbsParser::postfix() {
    shared_ptr<Operator> op;
    shared_ptr<Ast> ast;
    auto token = tokenQueue.peek();
    if (isPunct(token, ".")) {
        op = make_shared<Operator>(tokenQueue.poll());
        ast = name();
    } else if (isPunct(token, "(")) {
        op = make_shared<Operator>(token);
        ast = argumentList();
    } else if (isPunct(token, "[")) {
        op = make_shared<Operator>(token);
        ast = arrayIndex();
    } else {
        return vector<shared_ptr<Ast>>{};
    }
    return vector<shared_ptr<Ast>>{op, ast};
}

/**
 * parameter_list := "(" { name type_tag [ "," name type_tag ] } ")"
 */
shared_ptr<ParameterList> AbsParser::parameterList() {
    auto ret = skipPunct("(");
    CHECK_FALSE_RETURN(ret, nullptr);

    // no param
    if (isPunct(tokenQueue.peek(), ")")) {
        tokenQueue.poll();
        return make_shared<ParameterList>();
    }

    // has param
    vector<shared_ptr<Ast>> paramList;

    auto name = this->name();
    CHECK_NULL_RETURN(name, nullptr);
    paramList.push_back(name);
    auto typeTag = this->typeTag();
    CHECK_NULL_RETURN(typeTag, nullptr);
    paramList.push_back(typeTag);

    while (true) {
        if (!isPunct(tokenQueue.peek(), ",")) {
            break;
        }
        tokenQueue.poll();

        name = this->name();
        CHECK_NULL_RETURN(name, nullptr);
        paramList.push_back(name);
        typeTag = this->typeTag();
        CHECK_NULL_RETURN(typeTag, nullptr);
        paramList.push_back(typeTag);
    }

    ret = skipPunct(")");
    CHECK_FALSE_RETURN(ret, nullptr);

    return make_shared<ParameterList>(paramList);
}

/**
 * argument_list := "(" [ expr { "," expr } ] } ")"
 */
shared_ptr<ArgumentList> AbsParser::argumentList() {
    auto ret = skipPunct("(");
    CHECK_FALSE_RETURN(ret, nullptr);

    if (isPunct(tokenQueue.peek(), ")")) {
        tokenQueue.poll();
        return make_shared<ArgumentList>();
    }

    auto argList = vector<shared_ptr<Ast>>();
    auto expr = this->expr();
    CHECK_NULL_RETURN(expr, nullptr);
    argList.push_back(expr);

    while (true) {
        if (!isPunct(tokenQueue.peek(), ",")) {
            break;
        }

        tokenQueue.poll();
        expr = this->expr();
        CHECK_NULL_RETURN(expr, nullptr);
        argList.push_back(expr);
    }

    ret = skipPunct(")");
    CHECK_FALSE_RETURN(ret, nullptr);

    return make_shared<ArgumentList>(argList);
}

/**
 * array_list := "[" expr "]
 */
shared_ptr<ArrayIndex> AbsParser::arrayIndex() {
    auto ret = skipPunct("[");
    CHECK_FALSE_RETURN(ret, nullptr);

    auto expr = this->expr();
    CHECK_NULL_RETURN(expr, nullptr);

    ret = skipPunct("]");
    CHECK_FALSE_RETURN(ret, nullptr);

    return make_shared<ArrayIndex>(vector<shared_ptr<Ast>>{expr});
}

} // namespace Parser
